//
// Stage.cs
//
// Copyright (C) 2008 Lukas Lipka <lukaslipka@gmail.com>
//

using System;
using System.Collections.Generic;

namespace Dashboard.Mint.Theatrics {

	public class Stage<T> {

		// With a maximum FPS of 26, I have found
		// the rendering to be smooth enough without
		// much overhead.
		private const uint max_framerate = 26;

		private List<Actor<T>> actors = new List<Actor<T>> ();

		private DateTime start;
		private DateTime stop;

		private uint timeout_id = 0;
		private uint frames = 0;

		public event EventHandler Started;
		public event EventHandler Stopped;
		public event EventHandler Iteration;

		public Stage ()
		{
		}

		public void Start ()
		{
			Reset ();

			this.timeout_id = GLib.Timeout.Add (1000 / max_framerate, new GLib.TimeoutHandler (OnStep));

			if (Started != null)
				Started (this, EventArgs.Empty);
		}

		public void Stop ()
		{
			if (timeout_id < 1)
				return;

			GLib.Source.Remove (timeout_id);
			this.timeout_id = 0;
			
			this.stop = DateTime.Now;

			if (Stopped != null)
				Stopped (this, EventArgs.Empty);
		}

		public void Reset ()
		{
			if (timeout_id > 0)
				Stop ();

			lock (actors) {
				foreach (Actor<T> actor in actors) {
					actor.Reset ();
				}
			}

			this.start = DateTime.Now;
			this.stop = DateTime.Now;
			this.frames = 0;
		}

		public void Add (Actor<T> actor)
		{
			lock (actors) {
				if (actors.Contains (actor))
					throw new InvalidOperationException ("Stage already contains this actor");

				actors.Add (actor);
			}
		}

		public void Remove (Actor<T> actor)
		{
			lock (actors) {
				if (!actors.Contains (actor))
					throw new InvalidOperationException ("Stage does not contain this actor");

				actors.Remove (actor);
			}
		}

		private bool OnStep ()
		{
			lock (actors) {
				Queue<Actor<T>> expired_actors = new Queue<Actor<T>> ();
			
				// Step all active actors and check if any of
				// them have expired.
				
				foreach (Actor<T> actor in actors) {
					actor.Step ();
					
					if (actor.Expired) {
						expired_actors.Enqueue (actor);
					}
				}

				// Remove all expired actors
				
				if (expired_actors.Count > 0) {
					actors.Remove (expired_actors.Dequeue ());
				}

				frames++;

				if (Iteration != null)
					Iteration (this, EventArgs.Empty);
				
				// Stop the show if we run out of actors
				
				if (actors.Count < 1) {
					Stop ();
					return false;
				}
			}

			return true;
		}

		public bool Finished {
			get { return actors.Count == 0; }
		}

		public double FramesPerSecond {
			get { return frames / (stop - start).TotalSeconds; }
		}

		public double TotalDuration {
			get { return (stop - start).TotalMilliseconds; }
		}

		public int ActorCount {
			get { return actors.Count; }
		}
	}
}
